﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.Practices.CompositeUI.Instrumentation;
using Microsoft.Practices.CompositeUI.Properties;
using Microsoft.Practices.CompositeUI.Utility;
using Microsoft.Practices.ObjectBuilder;

namespace Microsoft.Practices.CompositeUI.EventBroker
{
    /// <summary>
    /// 	Represents a point of communication on a certain topic between the topic publishers and the topic subscribers.
    /// </summary>
    public class EventTopic : IDisposable, IBuilderAware
    {
        #region Fields & Properties

        private string name;

        /// <summary>
        /// 	Gets the topic name.
        /// </summary>
        public string Name
        {
            get
            {
                return name;
            }
        }

        private bool enabled = true;

        /// <summary>
        /// 	Gets or sets the enabled state of the topic. A disable topic will not fire events.
        /// </summary>
        public bool Enabled
        {
            get
            {
                return enabled;
            }
            set
            {
                enabled = value;
            }
        }

        private TraceSource traceSource;

        /// <summary>
        /// 	Sets the <see cref = "TraceSource" /> to use for information tracing.
        /// </summary>
        [ClassNameTraceSource]
        public TraceSource TraceSource
        {
            set
            {
                traceSource = value;
            }
            protected get
            {
                return traceSource;
            }
        }

        /// <summary>
        /// 	Gets the count of registered publications with this <see cref = "EventTopic" />.
        /// </summary>
        public virtual int PublicationCount
        {
            get
            {
                Clean();
                return publications.Count;
            }
        }

        #endregion

        #region Collection Fields & Properties

        private readonly List<Publication> publications = new List<Publication>();
        private readonly List<WorkItemSubscriptions> workItemSubscriptions = new List<WorkItemSubscriptions>();

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// 	Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// 	Called to free resources.
        /// </summary>
        /// <param name = "disposing">Should be true when calling from Dispose().</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                foreach (var publication in publications)
                {
                    publication.Dispose();
                }
                publications.Clear();
                workItemSubscriptions.Clear();
            }
        }

        #endregion

        #region Implementation of IBuilderAware

        /// <summary>
        /// 	See <see cref = "IBuilderAware.OnBuiltUp" /> for more information.
        /// </summary>
        public virtual void OnBuiltUp(string id)
        {
            name = id;
        }

        /// <summary>
        /// 	See <see cref = "IBuilderAware.OnTearingDown" /> for more information.
        /// </summary>
        public virtual void OnTearingDown()
        {
        }

        #endregion

        #region Add Publication

        /// <summary>
        /// 	Adds a publication to the topic.
        /// </summary>
        /// <param name = "publisher">The object that publishes the event that will fire the topic.</param>
        /// <param name = "eventName">The name of the event.</param>
        /// <param name = "workItem">The <see cref = "WorkItem" /> where the publisher is.</param>
        /// <param name = "scope">A <see cref = "PublicationScope" /> value which states scope for the publication.</param>
        public virtual void AddPublication(object publisher, string eventName, WorkItem workItem, PublicationScope scope)
        {
            Guard.ArgumentNotNull(publisher, "publisher");
            Guard.ArgumentNotNullOrEmptyString(eventName, "eventName");
            Guard.ArgumentNotNull(workItem, "workItem");
            Guard.EnumValueIsDefined(typeof (PublicationScope), scope, "scope");
            Clean();
            if (FindPublication(publisher, eventName) != null)
            {
                throw new EventBrokerException(Resources.OnlyOnePublicationIsAllowed);
            }
            var publication = new Publication(this, publisher, eventName, workItem, scope);
            publications.Add(publication);
            if (TraceSource != null)
            {
                TraceSource.TraceInformation(Resources.EventTopicTracePublicationAdded, name, eventName, publisher.GetType().ToString());
            }
        }

        #endregion

        #region Remove Publication

        #endregion

        #region Contains & Find Publication

        /// <summary>
        /// 	Checks if the specified publication has been registered with this <see cref = "EventTopic" />.
        /// </summary>
        /// <param name = "publisher">The object that contains the publication.</param>
        /// <param name = "eventName">The name of event on the publisher that fires the topic.</param>
        /// <returns>true if the topic contains the requested publication; otherwise false.</returns>
        public virtual bool ContainsPublication(object publisher, string eventName)
        {
            Guard.ArgumentNotNull(publisher, "publisher");
            Guard.ArgumentNotNull(eventName, "eventName");
            Clean();
            return FindPublication(publisher, eventName) != null;
        }

        private Publication FindPublication(object publisher, string eventName)
        {
            return publications.FirstOrDefault(publication => publication.Publisher == publisher && publication.EventName == eventName);
        }

        #endregion

        #region Clean

        /// <summary>
        /// 	Perform a sanity cleaning of the dead references to publishers and subscribers
        /// </summary>
        /// <devdoc>As the topic maintains <see cref = "WeakReference" /> to publishers and subscribers,
        /// 	those instances that are finished but hadn't been removed from the topic will leak. This method
        /// 	deals with that case.</devdoc>
        private void Clean()
        {
            foreach (var subscriptions in workItemSubscriptions.ToArray())
            {
                subscriptions.Clean();
                if (subscriptions.SubscriptionCount == 0)
                {
                    workItemSubscriptions.Remove(subscriptions);
                }
            }
            foreach (var publication in publications.ToArray())
            {
                if (publication.Publisher == null || publication.WorkItem == null || (publication.WorkItem != null && publication.WorkItem.Status == WorkItemStatus.Terminated))
                {
                    publications.Remove(publication);
                    publication.Dispose();
                }
            }
        }

        #endregion

        #region ClassNameTraceSourceAttribute Class

        private class ClassNameTraceSourceAttribute : TraceSourceAttribute
        {
            public ClassNameTraceSourceAttribute() : base(typeof (EventTopic).FullName)
            {
            }
        }

        #endregion
    }
}